#include "application.h"

#include <QtGui>
#include <QtCore>
#include <QtScript>
#include <QGLWidget>
#include <QtNetwork>
#include <QApplication>
#include <QDesktopWidget>

#include <optra/all>

#include "getopt.h"

#include "applicationsmanager.h"
#include "zonesmanager.h"
#include "servertask.h"
#include "tasksmanager.h"
#include "proxyimpl.h"
#include "optrzone.h"
#include "tcpserver.h"
#include "shell.h"

#include "expandingwindow.h"
#include "menubutton.h"

#include <QMessageBox>

#include <QPointF>
#include "toolbar.h"
#include "scenetoolbardata.h"
Optra::Proxy* proxy = NULL;

O_Z;

#define optra() OZ


class ApplicationPrivate{
public:
    ApplicationPrivate():
            rootState(0), ellipseState(0),
            figure8State(0), randomState(0),
            tiledState(0), centeredState(0),
            stateMachine(0), tray(0), buttonParent(0), previewItem(0)
    {};

    ~ApplicationPrivate(){
        delete rootState;
        //        delete ellipseState;
        //        delete figure8State;
        //        delete randomState;
        //        delete tiledState;
        //        delete centeredState;

        delete stateMachine;
        //delete tray;
        delete buttonParent;
    };

    QList<GraphicView*> primary_views;
    QList<Scene*> primary_scenes;


    QSharedPointer<Optra::Proxy> proxy;
    QSharedPointer<ApplicationsManager> apps;
    QSharedPointer<ZonesManager> zones;
    QSharedPointer<TasksManager> tasks;
    QSharedPointer<TcpServer> server;

    QState *rootState;
    QState *ellipseState;
    QState *figure8State;
    QState *randomState;
    QState *tiledState;
    QState *centeredState;

    QStateMachine* stateMachine;

    QSystemTrayIcon* tray;

    QMap<QString, QGraphicsProxyWidget*> views;

    QGraphicsItem *buttonParent;
    QList<QAction*> actions;

    bool noZonesLoad;

    QGraphicsProxyWidget* currentView;

    QGraphicsScene* previewScene;

    QGraphicsItem* previewItem;

    SceneToolbarData* preview;

};

Application::Application(int argc, char **argv) :
        QObject(0), O_PRIVATE_CREATE(Application)
{
    //O_D(Application);
    // QShortcut* shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_P), qApp->desktop());
    //connect(shortcut, SIGNAL(activated()), this, SLOT(hideOptra()));
}

Application::~Application()
{
    uninitialize();
    O_PRIVATE_DELETE;
}

void Application::showOptra()
{
    qDebug() << "Show Optra";
}

void Application::hideOptra()
{
    qDebug() << "Hide Optra";
}

ZonesManager* Application::zones()const
{
    O_D(Application);
    return d->zones.data();
}

TasksManager* Application::tasks()const
{
    O_D(Application);
    return d->tasks.data();
}

void Application::toogleFullScreen()
{
    if (primaryView()->isFullScreen()){
        primaryView()->showNormal();
        // primaryView()->setWindowFlags(primaryView()->windowFlags() &~Qt::WindowStaysOnTopHint);
    }else{
        primaryView()->showFullScreen();
        //primaryView()->setWindowFlags(primaryView()->windowFlags() | Qt::WindowStaysOnTopHint);
    }
}

void Application::initialize()
{
    O_D(Application);

    GetOpt opts(qApp->arguments());
    checkCommandLine(&opts, true);

    QFile style(":styles/style");
    style.open(QFile::ReadOnly);
    qApp->setStyleSheet(style.readAll());


    d->tray = new QSystemTrayIcon(qApp->desktop());
    d->tray->setIcon(QIcon(":/icons/main"));
    d->tray->show();

    d->proxy = QSharedPointer<Optra::Proxy>(new ProxyImpl(this));
    ::proxy = d->proxy.data();

    d->tasks = QSharedPointer<TasksManager>(new TasksManager(this));

    d->zones = QSharedPointer<ZonesManager>(new ZonesManager(this));
    d->apps = QSharedPointer<ApplicationsManager>(new ApplicationsManager(this));

    d->server = QSharedPointer<TcpServer>(new TcpServer(this));



    if (!d->noZonesLoad)
        d->zones->loadZones();


    if (!d->server->listen(QHostAddress::Any, 51999)) {
        QMessageBox::critical(qApp->desktop(), "Threaded Server", QString("Unable to start the server: %1.").arg(d->server->errorString()));
    }

    qDebug() << QString("The server is running on\n\nIP: %1\nport: %2\n").arg(d->server->serverAddress().toString()).arg(d->server->serverPort());


    setupDesktops();

    d->rootState = new QState;
    d->ellipseState = new QState(d->rootState);
    d->figure8State = new QState(d->rootState);
    d->randomState = new QState(d->rootState);
    d->tiledState = new QState(d->rootState);
    d->centeredState = new QState(d->rootState);
    d->stateMachine = new QStateMachine;

    d->stateMachine->addState(d->rootState);
    d->stateMachine->setInitialState(d->rootState);
    d->rootState->setInitialState(d->ellipseState);


    setupViews();
    createPreview();
    createMenu();
    // tasks->startApplication("battleship");

    QAction* a = new QAction(QIcon(":/icons/main"), "Full Screen", this);
    connect(a, SIGNAL(triggered()), this, SLOT(toogleFullScreen()));
    d->actions.append(a);
    primaryScene()->setToolbar(d->actions, Scene::top, 0);

    menuClicked("optra#welcome");
    updateView();
    hidePreview("");
    //primaryView()->grabMouse();
   // primaryView()->grabKeyboard();
    //primaryView()->grabShortcut();
    //primaryView()->grabGesture();
}

void Application::uninitialize()
{
    O_D(Application);
    d->server->close();
    d->server.clear();

    foreach(GraphicView* v, d->primary_views){
        delete v;
    }
    d->primary_views.clear();

    //    foreach(Scene* s, primary_scenes){
    //        delete s;
    //    }
    d->primary_scenes.clear();

    // delete d->tray;

    d->apps.clear();
    d->zones.clear();
    d->tasks.clear();
    d->proxy.clear();
}

void Application::createPreview()
{
    O_D(Application);
    d->preview = new SceneToolbarData;

    d->previewScene = new QGraphicsScene(0, 0, 100, 100);
    QGraphicsView* view = new QGraphicsView(d->previewScene);

    Toolbar* t = new Toolbar(Toolbar::sqare);

    QGraphicsProxyWidget* gpw = new QGraphicsProxyWidget(0, Qt::ToolTip);
    gpw->setWidget(view);
    gpw->resize(200, 170);
    gpw->show();
    d->previewScene->setSceneRect(gpw->boundingRect().adjusted(10, 10, -20, -20));
    t->setContent(gpw);
    t->setPos(0, 200);

    d->preview->toolbar = t;
    d->preview->invisible = new QPropertyAnimation(d->preview, "opacity");

    primaryScene()->addItem(gpw);
    primaryScene()->addItem(t);

}

GraphicView* Application::primaryView()
{
    O_D(Application);
    Q_ASSERT(!d->primary_views.isEmpty());

    return d->primary_views.at(0);
}

Scene* Application::primaryScene()
{
    O_D(Application);
    Q_ASSERT(!d->primary_scenes.isEmpty());

    return d->primary_scenes.at(0);
}

void Application::setupDesktops()
{
    O_D(Application);
    int count = qApp->desktop()->screenCount();
    for (int i = 0; i < count; i++){

        GraphicView* view = new GraphicView(this);
        view->setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
        view->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
        view->setCacheMode(QGraphicsView::CacheBackground);
        view->setRenderHints(view->renderHints() | QPainter::Antialiasing | QPainter::SmoothPixmapTransform);




        Scene * scene = new Scene;
        view->installEventFilter(this);
        //view->installEventFilter(scene);

        scene->installEventFilter(this);

        // view->setWindowOpacity(0.9);

        view->setScene(scene);

        d->primary_scenes.append(scene);
        d->primary_views.append(view);

        view->show();
        view->resize(QSize(1000, 700));
    }
}

void Application::addView(Optra::ZoneWindow * view)
{
    Q_ASSERT(view);
    O_D(Application);

    view->widget()->setup();
    QGraphicsProxyWidget* pwidget = new QGraphicsProxyWidget(0, Qt::ToolTip);
    pwidget->setWidget((QWidget*)view->widget());

    pwidget->setPos(0, 0);
    pwidget->resize(primaryScene()->sceneRect().size());
    pwidget->setFocusPolicy(Qt::NoFocus);
    primaryScene()->addItem(pwidget);

    if (view->toolbar() != NULL){
        Toolbar* toolbar = new Toolbar(Toolbar::horizontal);
        QGraphicsProxyWidget* prx = new QGraphicsProxyWidget(0, Qt::ToolTip);
        prx->setWidget(view->toolbar());
        primaryScene()->addItem(prx);
        toolbar->setContent(prx);
        primaryScene()->installToolbar(toolbar, Scene::bottom, 0, pwidget);
    }

    d->views.insert(view->id(), pwidget);

    QAction * a = new QAction(view->icon(), view->display(), this);
    connect(a, SIGNAL(triggered()), this, SLOT(toolbarClick()));
    a->setData(view->id());
    d->actions.append(a);
}

void Application::addTool(Optra::ZoneWindow * tool)
{
    Q_ASSERT(tool);
    O_D(Application);

    tool->widget()->setup();
    QGraphicsProxyWidget* pwidget = new QGraphicsProxyWidget(0, Qt::Dialog);
    pwidget->setWidget((QWidget*)tool->widget());

    pwidget->setPos(0, 0);
    pwidget->resize(primaryScene()->sceneRect().size());
    primaryScene()->addItem(pwidget);

    d->views.insert(tool->id(), pwidget);

    QAction * a = new QAction(tool->icon(), tool->display(), this);
    a->setData(tool->id());
    connect(a, SIGNAL(triggered()), this, SLOT(toolbarClick()));
    d->actions.append(a);
}

void Application::toolbarClick()
{
    QAction* a = (QAction*)sender();
    menuClicked(a->data().toString());
}

void Application::addMenu(QMenu* menu)
{

}

void Application::showMenu()
{
    O_D(Application);
    d->buttonParent->setVisible(true);
    emit menuRestore();
}

void Application::updateView()
{
    O_D(Application);
    foreach(QGraphicsProxyWidget*p, d->views.values()){
        p->resize(primaryScene()->sceneRect().size());
        d->buttonParent->setPos(primaryScene()->sceneRect().center());
    }
    QTimer::singleShot(20, primaryScene(), SLOT(refresh()));
}

void Application::showPreview(const QString& data)
{
    O_D(Application);

    //if (d->previewItem != NULL && d->previewItem != d->currentView)


    QGraphicsProxyWidget* p = d->views.value(data);
    if (p == NULL || p == d->previewItem || p == d->currentView){
        return;
    }

    hidePreview(data);

    d->previewItem = p;

    d->previewScene->addItem(p);
    //d->previewScene->addRect(QRectF(QPointF(0, 0), d->previewScene->sceneRect().size()));
    p->setPos(d->previewScene->sceneRect().topLeft());
    qreal scalew = d->previewScene->sceneRect().width() / p->size().width();
    qreal scaleh = d->previewScene->sceneRect().height() / p->size().height();
    p->setScale(scalew);

    d->preview->invisible->setStartValue(0.0f);
    d->preview->invisible->setEndValue(1.0f);
    d->preview->invisible->start();
}

void Application::hidePreview(const QString& data)
{
    O_D(Application);

    if (d->previewItem == NULL)
        return;


    d->previewItem->setScale(1.0f);
    primaryScene()->addItem(d->previewItem);
    d->previewItem = NULL;


    d->preview->invisible->setStartValue(1.0f);
    d->preview->invisible->setEndValue(0.0f);
    d->preview->invisible->start();

}

void Application::menuClicked(const QString& data)
{
    O_D(Application);
    QGraphicsProxyWidget* gpw = d->views.value(data);
    if (!gpw){
        optra()->debug(QString("Null view:%1").arg(data));
        return;
    }

    hidePreview(data);
    foreach(QGraphicsProxyWidget*p, d->views.values()){
        p->setZValue(-1);
    }
    d->currentView = gpw;
    primaryScene()->setActiveItem(gpw);
    gpw->setZValue(50);
    //gpw->setFocus();
    gpw->resize(primaryScene()->sceneRect().size());
    gpw->setPos(0, 0);

    //primaryView()->ensureVisible(gpw->toGraphicsObject());
    //QRectF br = d->buttonParent->boundingRect();
    //QRectF sr = primaryScene()->sceneRect();

    optra()->debug(QString("Activate view:%1").arg(data));

    emit menuStateChange();
}

void Application::createMenu()
{
    O_D(Application);
    d->buttonParent = new QGraphicsEllipseItem;
    ZonesManager::ZoneProcessMap m = zones()->zoneProcess();

    /*
QPointF(cos((i / 63.0) * 6.28) * 250,
                                                  sin((i / 63.0) * 6.28) * 250));
      */

    QList<MenuButton*> v;
    foreach(ZoneProcess p, m){
        foreach(Optra::ZoneWindow * window, p.info->views()){
            MenuButton * bt = new MenuButton(window->icon().pixmap(QSize(90, 90)), d->buttonParent);
            bt->setData(window->id());
            v.append(bt);
        }

        foreach(Optra::ZoneWindow * window, p.info->tools()){
            MenuButton * bt = new MenuButton(window->icon().pixmap(QSize(90, 90)), d->buttonParent);
            bt->setData(window->id());
            v.append(bt);
        }
    }

    //ellipseState = new QState(rootState);
    //figure8State = new QState(rootState);
    //randomState = new QState(rootState);
    //tiledState = new QState(rootState);
    //centeredState = new QState(rootState);

    QParallelAnimationGroup *group = new QParallelAnimationGroup;
    qreal count = (qreal)v.count();
    for (int i = 0; i < v.count(); i++){

        MenuButton* btn = v.at(i);
        connect(btn, SIGNAL(pressed(const QString&)), SLOT(menuClicked(const QString&)));
        connect(btn, SIGNAL(hover(const QString&)), SLOT(showPreview(const QString&)));
        connect(btn, SIGNAL(leave(const QString&)), SLOT(hidePreview(const QString&)));


        QPointF point = QPointF(cos((i / count) * 6.28) * 300, sin((i / count) * 6.28) * 300);
        //btn->setPos(point);


        d->ellipseState->assignProperty(btn, "pos", point);
        d->ellipseState->assignProperty(btn, "opacity", 1.0);

        d->tiledState->assignProperty(btn, "opacity", 0.0);
        d->tiledState->assignProperty(btn, "pos",
                                      QPointF(((i % 4) - 2) * btn->geometry().width() + btn->geometry().width() / 2,
                                              ((i / 4) - 2) * btn->geometry().height() + btn->geometry().height() / 2));

        d->randomState->assignProperty(btn, "pos",
                                       QPointF(-250 + qrand() % 500,
                                               -250 + qrand() % 500));


        QPropertyAnimation *anim = new QPropertyAnimation(btn, "pos");
        anim->setDuration(750 + i * 25);
        anim->setEasingCurve(QEasingCurve::InOutBack);
        group->addAnimation(anim);

        anim = new QPropertyAnimation(btn, "opacity");
        anim->setStartValue(1.0);
        anim->setEndValue(0.0);
        group->addAnimation(anim);

    }

    primaryScene()->addItem(d->buttonParent);
    primaryScene()->setMainMenu(d->buttonParent);

    d->buttonParent->scale(0.5, 0.5);
    d->buttonParent->setPos(300, 200);
    d->buttonParent->setZValue(65);

    QAbstractTransition *trans = d->ellipseState->addTransition(this, SIGNAL(menuStateChange()), d->tiledState);
    trans->addAnimation(group);

    trans = d->tiledState->addTransition(this, SIGNAL(menuRestore()), d->ellipseState);
    trans->addAnimation(group);

    //    trans = tiledState->addTransition(this, SIGNAL(menuStateChange()), ellipseState);
    //    trans->addAnimation(group);


    d->stateMachine->start();
    updateView();
}

void Application::setupViews()
{
    O_D(Application);
    ZonesManager::ZoneProcessMap m = d->zones->zoneProcess();
    foreach(ZoneProcess p, m){

        Optra::ZoneInfo* zinfo = p.info;
        Optra::ZoneInterface* zi = p.zinterface;
        Optra::ZoneInfo::ViewsList vl = zinfo->views();
        Optra::ZoneInfo::ToolsList tl = zinfo->tools();


        foreach(Optra::ZoneWindow * window, vl){
            addView(window);
        }

        foreach(Optra::ZoneWindow * window, tl){
            addTool(window);
        }

    }
}


void Application::connectToZone(const QString& name, const char * slot, const QObject * sender, const char * signal, Qt::ConnectionType type)
{
    O_D(Application);
    ZonesManager::ZoneProcessMap m = d->zones->zoneProcess();
    if (!m.contains(name)){
        optra()->trace(QString("connect: no existed zone (zone:%1 signal:%2)").arg(name).arg(signal));
        return;
    }

    QObject::connect(sender, signal, (QObject*)m.value(name).zinterface, slot, type);
    optra()->trace(QString("connect: to zone (zone:%1 signal:%2)").arg(name).arg(signal));
}


void Application::handleMessage(const QString& message)
{
    //O_D(Application);
    GetOpt opts(message.split(" "));
    checkCommandLine(&opts, false);
    optra()->info(message);
}


void Application::checkCommandLine(GetOpt* line, bool init)
{
    O_D(Application);
    Q_ASSERT(line);

    if (init){

        line->addSwitch("nozones", &d->noZonesLoad);

    }else{

    }

    //if (!line->parse()){
    //        qDebug() << "Command line error";
    //      return;
    //    }
}


ExpandingWindow* Application::createToolbar(QWidget* parent, int type)
{
    //O_D(Application);
    return new ExpandingWindow(parent, type);
}


void Application::keyPressEvent ( QKeyEvent * event )
{
    //O_D(Application);
    switch(event->key()){
    case Qt::Key_F1:
        showMenu();
        break;
    case Qt::Key_Escape:
        primaryView()->setFocus();
        break;
    }
    event->accept();
}


bool Application::event(QEvent * e)
{
    //O_D(Application);
    if (e->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
        qDebug("Ate key press %d", keyEvent->key());
    }

    return QObject::event(e);
}
